/** 
 *  Copyright 2010 Matthew Werny & Burk Price.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package net.iceflow.db.migration.resolver;

import net.iceflow.db.migration.version.extractor.SimpleVersionExtractor;
import net.iceflow.db.migration.version.VersionExtractor;
import net.iceflow.db.migration.exception.MigrationException;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.springframework.util.StringUtils.collectionToCommaDelimitedString;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.iceflow.db.jdbc.DatabaseType;
import net.iceflow.db.migration.MigrationExecuter;
import net.iceflow.db.migration.MigrationFactory;
import net.iceflow.db.migration.MigrationResolver;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

/**
 * A MigrationResolver which leverages Spring's robust Resource loading
 * mechanism, supporting 'file:', 'classpath:', and standard url format
 * resources.
 * <p/>
 * Migration Location Examples:
 * <ul>
 * <li>classpath:/db/migrations/</li>
 * <li>file:src/main/db/migrations/</li>
 * <li>file:src/main/resources/db/migrations/</li>
 * </ul>
 * All of the resources found in the migrations location which do not start with
 * a '.' will be considered migrations.
 * <p/>
 * Configured out of the box with a SimpleVersionExtractor and the default
 * resource pattern CLASSPATH_MIGRATIONS_SQL.
 * 
 * @see Resource
 * @see PathMatchingResourcePatternResolver
 * @see VersionExtractor
 * @see MigrationFactory
 */
public class ResourceMigrationResolver implements MigrationResolver {

    private static final String CLASSPATH_MIGRATIONS_SQL = "classpath:/db/migrations/";
    protected final Logger logger = Logger.getLogger(getClass());
    private String migrationsLocation;
    private VersionExtractor versionExtractor;
    private MigrationFactory migrationFactory = new MigrationFactory();

    public ResourceMigrationResolver() {
        this(CLASSPATH_MIGRATIONS_SQL);
    }

    public ResourceMigrationResolver(String migrationsLocation) {
        this(migrationsLocation, new SimpleVersionExtractor());
    }

    public ResourceMigrationResolver(String migrationsLocation, VersionExtractor versionExtractor) {
        setMigrationsLocation(migrationsLocation);
        setVersionExtractor(versionExtractor);
    }

    public Set<MigrationExecuter> resolve(DatabaseType dbType) {
        Set<MigrationExecuter> migrations = new HashSet<MigrationExecuter>();

        // Find all resources in the migrations location.
        String convertedMigrationsLocation = convertMigrationsLocation(migrationsLocation);

        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        List<Resource> resources;
        try {
            resources = new ArrayList<Resource>(Arrays.asList(patternResolver.getResources(convertedMigrationsLocation)));
        } catch (IOException e) {
            throw new MigrationException(e);
        }

        // Remove resources starting with a '.' (e.g. .svn, .cvs, etc)
        CollectionUtils.filter(resources, new Predicate() {

            public boolean evaluate(Object object) {
                try {
                    return !(((Resource) object).getFilename().startsWith(".") || (((Resource) object).getFile().isDirectory()));
                } catch (IOException e) {
                    return false;
                }
            }
        });

        if (resources.isEmpty()) {
            String message = "No migrations were found using resource pattern '" + migrationsLocation
                    + "'. Terminating migration.";
            logger.error(message);
            throw new MigrationException(message);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Found " + resources.size() + " resources: "
                    + collectionToCommaDelimitedString(resources));
        }

        // Extract versions and create executable migrations for each resource.
        for (Resource resource : resources) {
            String version = versionExtractor.extractVersion(resource.getFilename());
            migrations.add(migrationFactory.create(version, resource));
        }

        return migrations;
    }

    public Set<MigrationExecuter> resolve() {
        return resolve(DatabaseType.UNKNOWN);
    }

    String convertMigrationsLocation(String migrationsLocation) {
        String converted = migrationsLocation;

        if (!(isBlank(FilenameUtils.getName(converted)) || FilenameUtils.getName(converted).contains("*"))) {
            converted += "/";
        }

        if (!FilenameUtils.getName(converted).contains("*")) {
            converted += "*";
        }

        if (!(converted.startsWith("file:") || converted.startsWith("classpath:"))) {
            converted = "file:" + converted;
        }

        return converted;
    }

    public void setMigrationsLocation(String migrationsLocation) {
        this.migrationsLocation = migrationsLocation;
    }

    public void setVersionExtractor(VersionExtractor versionExtractor) {
        this.versionExtractor = versionExtractor;
    }

    public void setMigrationFactory(MigrationFactory migrationFactory) {
        this.migrationFactory = migrationFactory;
    }
}
